1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.hiprenderer.backend.gl.glrenderer;
12 
13 version(Android)
14 {
15     public import gles.gl30;
16 }
17 else version(PSVita)
18 {
19     public import gles;
20 }
21 else version(WebAssembly)
22 {
23     public import gles;
24 }
25 else version(Have_bindbc_opengl)
26 {
27     public import bindbc.opengl;
28 }
29 version(OpenGL):
30 
31 import hip.hiprenderer.config;
32 import hip.hiprenderer.renderer;
33 import hip.hiprenderer.framebuffer;
34 import hip.hiprenderer.shader;
35 import hip.hiprenderer.viewport;
36 import hip.windowing.window;
37 import hip.util.conv;
38 import hip.math.rect;
39 import hip.console.log;
40 import hip.error.handler;
41 
42 import hip.hiprenderer.backend.gl.gltexture;
43 import hip.hiprenderer.backend.gl.glframebuffer;
44 import hip.hiprenderer.backend.gl.glshader;
45 
46 private __gshared bool errorCheckEnabled = true;
47 
48 auto glCall(T)(scope T delegate() dg, string file = __FILE__, size_t line = __LINE__)
49 {
50     import hip.config.opts;
51     static if(is(T == void))
52         dg();
53     else
54         auto ret = dg();
55     version(WebAssembly)
56     {
57         static if(HIP_DEBUG_WEBGL)
58         {
59             if(errorCheckEnabled)
60                 HipRenderer.exitOnError(file, line);
61         }
62     }
63     else static if(HIP_DEBUG_GL)
64     {
65         if(errorCheckEnabled)
66             HipRenderer.exitOnError(file, line);
67     }
68     static if(!is(T == void))
69     return ret;
70 }
71 
72 GLenum fromHipStencilFunc(HipStencilTestingFunction fn)
73 {
74     final switch(fn) with(HipStencilTestingFunction)
75     {
76         case Never: return GL_NEVER;
77         case Always: return GL_ALWAYS;
78         case Less: return GL_LESS;
79         case LessEqual: return GL_LEQUAL;
80         case Greater: return GL_GREATER;
81         case GreaterEqual: return  GL_GEQUAL;
82         case Equal: return GL_EQUAL;
83         case NotEqual: return GL_NOTEQUAL;
84     }
85 }
86 
87 GLenum fromHipStencilOperation(HipStencilOperation op)
88 {
89     final switch(op) with(HipStencilOperation)
90     {
91         case Keep: return GL_KEEP;
92         case Zero: return GL_ZERO;
93         case Replace: return GL_REPLACE;
94         case Increment: return GL_INCR;
95         case IncrementWrap: return GL_INCR_WRAP;
96         case Decrement: return  GL_DECR;
97         case DecrementWrap: return GL_DECR_WRAP;
98         case Invert: return GL_INVERT;
99     }
100 }
101 
102 /**
103 *
104 *   Those functions here present are fairly inneficient as there is not batch ocurring,
105 *   as I don't understand how to implement it right now, I'll mantain those functions for having
106 *   static access to drawing
107 */
108 class Hip_GL3Renderer : IHipRendererImpl
109 {
110     HipWindow window;
111     Shader currentShader;
112     protected __gshared bool isGLBlendEnabled = false;
113     protected __gshared bool isGLDepthEnabled = false;
114     protected __gshared bool isGLStencilEnabled = false;
115     protected __gshared GLenum mode;
116 
117     void setErrorCheckingEnabled(bool enable = true){errorCheckEnabled = enable;}
118     public final bool isRowMajor(){return true;}
119 
120     Shader createShader()
121     {
122         version(HipGL3)
123             return new Shader(new Hip_GL3_ShaderImpl());
124         else
125             return new Shader(new Hip_GL_ShaderImpl());
126     }
127     ShaderVar* createShaderVar(ShaderTypes shaderType, UniformType uniformType, string varName, size_t length)
128     {
129         switch(uniformType) with(UniformType)
130         {
131             case texture_array:
132             {
133                 return ShaderVar.createBlackboxed(shaderType, varName, uniformType, GLuint.sizeof*length, GLuint.sizeof);
134             }
135             default: return null;
136         }
137     }
138     version(dll)public bool initExternal(){return init(null);}
139     public bool init(HipWindow window)
140     {
141         this.window = window;
142         if(window !is null)
143             window.startOpenGLContext();
144         version(Have_bindbc_opengl)
145         {
146             GLSupport ver = loadOpenGL();
147             if(ver == GLSupport.noLibrary)
148             {
149                 ErrorHandler.showErrorMessage("Loading OpenGL", "No OpenGL could be found");
150                 return false;
151             }
152             else if(ver == GLSupport.badLibrary)
153             {
154                 ErrorHandler.showErrorMessage("Loading OpenGL", "OpenGL version is different than expected");
155             }
156         }
157         rawlog("GL Renderer: ",  glGetString(GL_RENDERER));
158         rawlog("GL Version: ",  glGetString(GL_VERSION));
159         rawlog("GLSL Version: ",  glGetString(GL_SHADING_LANGUAGE_VERSION));
160 
161         // setColor();
162         HipRenderer.rendererType = HipRendererType.GL3;
163         return true;
164     }
165 
166     void setShader(Shader s)
167     {
168         currentShader = s;
169     }
170     public int queryMaxSupportedPixelShaderTextures()
171     {
172         version(PSVita)
173         {
174             return 1;
175         }
176         else
177         {
178             int maxTex;
179             glCall(() => glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTex));
180             return maxTex;
181         }
182     }
183 
184     public void setColor(ubyte r = 255, ubyte g = 255, ubyte b = 255, ubyte a = 255)
185     {
186         glCall(() => glClearColor(cast(float)r/255, cast(float)g/255, cast(float)b/255, cast(float)a/255));
187     }
188 
189     public IHipFrameBuffer createFrameBuffer(int width, int height)
190     {
191         return new Hip_GL3_FrameBuffer(width, height);
192     }
193 
194     public IHipVertexArrayImpl createVertexArray()
195     {
196         version(HipGLUseVertexArray)
197             return new Hip_GL3_VertexArrayObject();
198         else
199             return new Hip_GL_VertexArrayObject();
200     }
201     public IHipTexture createTexture()
202     {
203         return new Hip_GL3_Texture();
204     }
205     public IHipVertexBufferImpl createVertexBuffer(size_t size, HipBufferUsage usage)
206     {
207         return new Hip_GL3_VertexBufferObject(size, usage);
208     }
209     public IHipIndexBufferImpl createIndexBuffer(index_t count, HipBufferUsage usage)
210     {
211         return new Hip_GL3_IndexBufferObject(count, usage);
212     }
213 
214 
215     public void setViewport(Viewport v)
216     {
217         glCall(() => glViewport(cast(int)v.x, cast(int)v.y, cast(GLsizei)v.width, cast(GLsizei)v.height));
218     }
219     public bool setWindowMode(HipWindowMode mode)
220     {
221         final switch(mode) with(HipWindowMode)
222         {
223             case BORDERLESS_FULLSCREEN:
224                 break;
225             case FULLSCREEN:
226                 break;
227             case WINDOWED:
228 
229                 break;
230         }
231         return false;
232     }
233     public bool hasErrorOccurred(out string err, string file = __FILE__, size_t line =__LINE__)
234     {
235         GLenum errorCode = glGetError();
236         static enum GL_STACK_OVERFLOW = 0x0503;
237         static enum GL_STACK_UNDERFLOW = 0x0504;
238         switch(errorCode)
239         {
240             //Don't execute to!string(line) for avoiding useless GC trigger.
241             case GL_NO_ERROR:
242                 err =  `GL_NO_ERROR: No error has been recorded. The value of this symbolic constant is guaranteed to be 0.`;
243                 break;
244             case GL_INVALID_ENUM:
245                 err = `GL_INVALID_ENUM at `~file~":"~to!string(line)~`:
246     An unacceptable value is specified for an enumerated argument. The offending command is ignored and has no other side effect than to set the error flag.`;
247                 break;
248             case GL_INVALID_VALUE:
249                 err = `GL_INVALID_VALUE at `~file~":"~to!string(line)~`:
250     A numeric argument is out of range. The offending command is ignored and has no other side effect than to set the error flag.`;
251                 break;
252             case GL_INVALID_OPERATION:
253                 err = `GL_INVALID_OPERATION at `~file~":"~to!string(line)~`:
254     The specified operation is not allowed in the current state. The offending command is ignored and has no other side effect than to set the error flag.`;
255                 break;
256             case GL_INVALID_FRAMEBUFFER_OPERATION:
257                 err = `GL_INVALID_FRAMEBUFFER_OPERATION at `~file~":"~to!string(line)~`:
258     The framebuffer object is not complete. The offending command is ignored and has no other side effect than to set the error flag.`;
259                 break;
260             case GL_OUT_OF_MEMORY:
261                 err = `GL_OUT_OF_MEMORY at `~file~":"~to!string(line)~`:
262     There is not enough memory left to execute the command. The state of the GL is undefined, except for the state of the error flags, after this error is recorded.`;
263                 break;
264             case GL_STACK_UNDERFLOW:
265                 err = `GL_STACK_UNDERFLOW at `~file~":"~to!string(line)~`:
266     An attempt has been made to perform an operation that would cause an internal stack to underflow.`;
267                 break;
268             case GL_STACK_OVERFLOW:
269                 err = `GL_STACK_OVERFLOW at `~file~":"~to!string(line)~`:
270     An attempt has been made to perform an operation that would cause an internal stack to overflow.`;
271                 break;
272             default:
273                 err = "Unknown error code";
274         }
275         return errorCode != GL_NO_ERROR;
276     }
277 
278     /**
279     *   This function is used to control the internal state for creating vertex buffers
280     */
281     public void begin(){}
282 
283     /**
284     */
285     public void end()
286     {
287         static if(UseGLES)
288         {
289             version(PSVita)
290             {
291                 glCall(() => glFlush());
292                 glCall(() => glFinish());
293             }
294         }
295         else
296         {
297             window.rendererPresent();
298             // glCall(() => glFlush());
299             // glCall(() => glFinish());
300         }
301     }
302 
303     public void clear()
304     {
305         uint clearMask = GL_COLOR_BUFFER_BIT;
306         if(isGLDepthEnabled) clearMask|= GL_DEPTH_BUFFER_BIT;
307         if(isGLStencilEnabled) clearMask|= GL_STENCIL_BUFFER_BIT;
308 
309         glCall(() => glClear(clearMask));
310     }
311 
312     public void clear(ubyte r = 255, ubyte g = 255, ubyte b = 255, ubyte a = 255)
313     {
314         setColor(r,g,b,a);
315         clear();
316     }
317 
318     protected GLenum getGLRendererMode(HipRendererMode mode)
319     {
320         final switch(mode) with(HipRendererMode)
321         {
322             case POINT: return GL_POINTS;
323             case LINE: return GL_LINES;
324             case LINE_STRIP: return GL_LINE_STRIP;
325             case TRIANGLES: return GL_TRIANGLES;
326             case TRIANGLE_STRIP: return GL_TRIANGLE_STRIP;
327         }
328     }
329     public void setRendererMode(HipRendererMode mode)
330     {
331         this.mode = getGLRendererMode(mode);
332     }
333     /**
334     *   Offset is per byte based
335     */
336     public void drawVertices(index_t count, uint offset)
337     {
338         glCall(() => glDrawArrays(this.mode, offset, count));
339     }
340     /**
341     *   Offset will always be based per index_t.
342     */
343     public void drawIndexed(index_t indicesCount, uint offset = 0)
344     {
345         static if(is(index_t == uint))
346             glCall(() => glDrawElements(this.mode, indicesCount, GL_UNSIGNED_INT, cast(void*)(offset*index_t.sizeof)));
347         else
348             glCall(() => glDrawElements(this.mode, indicesCount, GL_UNSIGNED_SHORT, cast(void*)(offset*index_t.sizeof)));
349     }
350 
351     bool isBlendingEnabled() const {return isGLBlendEnabled;}
352     
353     public void dispose()
354     {
355         if(window !is null)
356         {
357             window.destroyOpenGLContext();
358         }
359     }
360     
361     public void setDepthTestingFunction(HipDepthTestingFunction)
362     {
363     }
364     public void setDepthTestingEnabled(bool bEnable)
365     {
366         isGLDepthEnabled = bEnable;
367         if(bEnable) glCall(() => glEnable(GL_DEPTH_TEST));
368         else glCall(() => glDisable(GL_DEPTH_TEST));
369     }
370 
371     public void setStencilTestingEnabled(bool bEnable)
372     {
373         // isGLStencilEnabled = bEnable;
374         isGLStencilEnabled = true;
375         GLboolean r = bEnable ? GL_FALSE : GL_TRUE;
376         // glCall(() => glColorMask(r, r, r, r));
377         // glCall(() => glDepthMask(r));
378         if(bEnable) glCall(() => glEnable(GL_STENCIL_TEST));
379         else glCall(() => glDisable(GL_STENCIL_TEST));
380         glClearStencil(0);
381     }
382 
383     public void setColorMask(ubyte r, ubyte g, ubyte b, ubyte a)
384     {
385         glCall(() => glColorMask(r>0, g>0, b>0, a>0));
386     }
387 
388     public void setStencilTestingMask(uint mask)
389     {
390         if(mask > 0xFF) mask = 0xFF;
391         glCall(() => glStencilMask(mask));
392     }
393 
394     public void setStencilTestingFunction(HipStencilTestingFunction passFunc, uint reference, uint mask)
395     {
396         glCall(() => glStencilFunc(passFunc.fromHipStencilFunc, reference, mask));
397     }
398 
399     public void setStencilOperation(HipStencilOperation stencilFail, HipStencilOperation depthFail, HipStencilOperation stencilAndDephPass)
400     {
401         glCall(() => glStencilOp(stencilFail.fromHipStencilOperation, depthFail.fromHipStencilOperation, stencilAndDephPass.fromHipStencilOperation));
402     }
403     
404 }